<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>XML美化</title>
     <!--不管你要引入什么主题配色都需要引入的css文件-->
    <link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css">
    <!--下面的css文件就是不同主题配色的css文件-->
    <link rel="stylesheet" href="node_modules/codemirror/theme/monokai.css">
    <!--不管你需要引入什么样式和主题都是必须引入的-->
　  <script src="node_modules/codemirror/lib/codemirror.js"></script>
　  <!--使用的是javascript样式的编辑器-->
    <script src="node_modules/codemirror/mode/xml/xml.js"></script>
  </head>
  <body>
    <button onclick="xmlClick()">XML美化</button>
    <textarea id = 'code' name="code"></textarea>
  </body>
  <script>
    var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
            lineNumbers: true,//是否显示每一行的行数
　　　　　　 readOnly: false,//只读
            styleActiveLine: true,
            matchBrackets: true
        });
    function xmlClick() {
      try {
        debugger
        var ugly = editor.getValue()
        var pretty = formateXml(ugly)
        editor.setValue(pretty)
      } catch (error) {
        window.alert('格式化失败')
      }
      //格式化xml代码
    function formateXml(xmlStr){
        text = xmlStr.replace(/[\r\n]/g,"");
        //使用replace去空格
        text = '\n' + text.replace(/(<\w+)(\s.*?>)/g,function($0, name, props){
            return name + ' ' + props.replace(/\s+(\w+=)/g," $1");
        }).replace(/>\s*?</g,">\n<");
        //处理注释
        text = text.replace(/\n/g,'\r').replace(/<!--(.+?)-->/g,function($0, text){
            var ret = '<!--' + escape(text) + '-->';
            return ret;
        }).replace(/\r/g,'\n');
        //调整格式  以压栈方式递归调整缩进
        var rgx = /\n(<(([^\?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/mg;
        var nodeStack = [];
        var output = text.replace(rgx,function($0,all,name,isBegin,isCloseFull1,isCloseFull2 ,isFull1,isFull2){
            var isClosed = (isCloseFull1 == '/') || (isCloseFull2 == '/' ) || (isFull1 == '/') || (isFull2 == '/');
            var prefix = '';
            if(isBegin == '!'){//!开头
                prefix = setPrefix(nodeStack.length);
            }else {
                if(isBegin != '/'){///开头
                    prefix = setPrefix(nodeStack.length);
                    if(!isClosed){//非关闭标签
                        nodeStack.push(name);
                    }
                }else{
                    nodeStack.pop();//弹栈
                    prefix = setPrefix(nodeStack.length);
                }
            }
            var ret =  '\n' + prefix + all;
            return ret;
        });
        var prefixSpace = -1;
        var outputText = output.substring(1);
        //还原注释内容
        outputText = outputText.replace(/\n/g,'\r').replace(/(\s*)<!--(.+?)-->/g,function($0, prefix,  text){
            if(prefix.charAt(0) == '\r')
                prefix = prefix.substring(1);
            text = unescape(text).replace(/\r/g,'\n');
            var ret = '\n' + prefix + '<!--' + text.replace(/^\s*/mg, prefix ) + '-->';
            return ret;
        });
        outputText= outputText.replace(/\s+$/g,'').replace(/\r/g,'\r\n');
        return outputText;
    }
     
    //计算头函数 用来缩进
    function setPrefix(prefixIndex) {
        var result = '';
        var span = '    ';//缩进长度
        var output = [];
        for(var i = 0 ; i < prefixIndex; ++i){
            output.push(span);
        }
        result = output.join('');
        return result;
    }
    }
  </script>
  <style>
    html,body{
      height: 100%;
      margin: 0;
    }
    button{
      width: 100%;
      top: 0;
      left: 0;
      position:absolute;
      height: 5%;
      margin: 0;
      border: 0;
      padding: 0;
    }
    .CodeMirror {
      position:absolute;
      left: 0;
      top: 5%;
      margin: 0;
      border: 0;
      padding: 0;
      width: 100%;
      height: 95%;
    }
  </style>
</html>
